/*
 * Copyright (C) 2012 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may obtain
 * a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package com.facebook.swift.codec.metadata;

import com.facebook.swift.codec.metadata.ThriftCatalog;
import com.facebook.swift.service.metadata.ThriftMethodMetadata;
import com.facebook.swift.service.metadata.ThriftServiceMetadata;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.*;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;

import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

import static com.google.common.base.Preconditions.checkState;

/**
 * 重载所有{@link ThriftMethodMetadata}相关的方法，
 * 返回{@link ThriftMethodMetadata}对象时替换为{@link DecoratorThriftMethodMetadata},
 * @author guyadong
 *
 */
@Immutable
public class DecoratorThriftServiceMetadata extends ThriftServiceMetadata
{
	private final Class<?> serviceClass;
	private final ThriftCatalog catalog;
    /** 
     * {@link DecoratorThriftMethodMetadata}缓存对象,
     * 保存每个{@link ThriftMethodMetadata}对应的{@link DecoratorThriftMethodMetadata}实例 
     */
	private final LoadingCache<ThriftMethodMetadata,DecoratorThriftMethodMetadata> 
		methodCache = CacheBuilder.newBuilder().build(new CacheLoader<ThriftMethodMetadata,DecoratorThriftMethodMetadata>(){
			@Override
			public DecoratorThriftMethodMetadata load(ThriftMethodMetadata key) throws Exception {
				return new DecoratorThriftMethodMetadata(getName(), key.getMethod(), catalog);
			}});
	/** {@link ThriftMethodMetadata}转为{@link DecoratorThriftMethodMetadata}对象  */
    private final Function<ThriftMethodMetadata, ThriftMethodMetadata> 
    	methodMetadataTransformer = new Function<ThriftMethodMetadata, ThriftMethodMetadata>() {
		    @Nullable
		    @Override
		    public ThriftMethodMetadata apply(@Nullable ThriftMethodMetadata input){
		        return null == input  || input instanceof DecoratorThriftMethodMetadata
		        		? input 
		        		: methodCache.getUnchecked(input);
		    }
		};
	private final Predicate<String> methodFilter;
	private volatile Map<String, ThriftMethodMetadata> methods;
	private volatile Map<String, ThriftMethodMetadata> declaredMethods ;
	private volatile ImmutableList<String> docs;
	private final boolean sortByInclude;
	public DecoratorThriftServiceMetadata(Class<?> serviceClass, ThriftCatalog catalog, 
			Iterable<String> excludeMethods, Iterable<String> includeMethods, boolean sortByInclude){
    	super(serviceClass, catalog);
    	this.serviceClass = serviceClass;
    	this.catalog = catalog;
    	if(excludeMethods != null && excludeMethods.iterator().hasNext()){
    		this.methodFilter = new NameExcludeFilter(excludeMethods);
    	}else if (includeMethods != null && includeMethods.iterator().hasNext()){
    		this.methodFilter = new NameIncludeFilter(includeMethods);
    	}else{
    		this.methodFilter = new NameExcludeFilter(Collections.<String>emptySet());
    	}
    	this.sortByInclude = sortByInclude;
    }
	/**
	 * for compatibility
	 */
	public DecoratorThriftServiceMetadata(Class<?> serviceClass, ThriftCatalog catalog, 
			Iterable<String> excludeMethods, Iterable<String> includeMethods){
		this(serviceClass, catalog, excludeMethods, includeMethods, false);
	}
	
    @Override
    public ThriftMethodMetadata getMethod(String name){
        return getMethods().get(name);
    }
    @Override
    public Map<String, ThriftMethodMetadata> getMethods(){
    	// double check
    	if(methods == null){
    		synchronized (this) {
				if(methods == null){
					Map<String, ThriftMethodMetadata> filtered = Maps.filterKeys(DecoratorThriftServiceMetadata.super.getMethods(), methodFilter);
					methods = Maps.transformValues(filtered, methodMetadataTransformer);
				
				}
			}
    	}
    	return methods;
    }
    @Override
    public Map<String, ThriftMethodMetadata> getDeclaredMethods(){
    	// double check
    	if(declaredMethods == null){
    		synchronized (this) {
				if(declaredMethods == null){
					Map<String, ThriftMethodMetadata> filtered = Maps.filterKeys(DecoratorThriftServiceMetadata.super.getDeclaredMethods(), methodFilter);
			    	declaredMethods = Maps.transformValues(filtered, methodMetadataTransformer);
			    	if(sortByInclude && methodFilter instanceof NameIncludeFilter){
			    		// 根据includeMethods排序
			    		LinkedHashMap<String, ThriftMethodMetadata>  m = new LinkedHashMap<>();
			    		for(String pattern:((NameIncludeFilter)methodFilter).getIncludeMethods()){
			    			for(Entry<String, ThriftMethodMetadata> entry:declaredMethods.entrySet()){
			    				String key = entry.getKey();
			    				ThriftMethodMetadata value = entry.getValue();
			    				if(Objects.equals(pattern, key) || PatternFilter.filter(pattern,key,false)){
			    					m.put(key, value);
			    				}
			    			}
			    		}
			    		checkState(m.size() == declaredMethods.size(),"MISMATCH SIZE of declaredMethods with size of sorted by includeMethods");
			    		declaredMethods = m;
			    	}
				}
			}
    	}
    	return declaredMethods;
    }
	@Override
	public ImmutableList<String> getDocumentation() {
		// double checking
		if(docs == null){
			synchronized (this) {
				if(docs == null){
					docs = super.getDocumentation();
					if(Decorators.javadocCommentProviderFactory  != null){
						if(docs == null || docs.isEmpty()){
							docs = Decorators.javadocCommentProviderFactory.apply(serviceClass).commentOfClass();
						}
					}
				}
			}
		}
		return docs;
	}
}
